package io.github.frailty9.crserver.dso.service.impl;

import com.mybatisflex.kotlin.extensions.db.baseMapper
import com.mybatisflex.kotlin.extensions.db.insert
import com.mybatisflex.kotlin.extensions.db.query
import com.mybatisflex.kotlin.extensions.db.queryOne
import com.mybatisflex.kotlin.extensions.kproperty.eq
import io.github.frailty9.crserver.model.ResourceInstance;
import io.github.frailty9.crserver.dso.service.ResourceInstanceService;
import io.github.frailty9.crserver.dso.service.ResourceInstanceTemplateService
import io.github.frailty9.crserver.dso.service.ResourceService
import io.github.frailty9.crserver.model.Resource
import io.github.frailty9.crserver.model.ResourceInstanceTemplate
import io.github.frailty9.crserver.utils.DateUtils
import org.noear.solon.annotation.Component;
import org.noear.solon.data.annotation.Transaction
import org.noear.solon.scheduling.annotation.Scheduled
import java.time.DayOfWeek
import java.time.LocalDate

/**
 * ResourceInstanceServiceImpl
 */
@Component
open class ResourceInstanceServiceImpl(
    private val resourceService: ResourceService,
    private val resourceInstanceTemplateService: ResourceInstanceTemplateService
) : ResourceInstanceService {

    /**
     * 用于定时任务调用： 生成未来 10 天内所有资源实例
     * 计划任务: 每日 1:00 执行一次
     */
    @Transaction
    @Scheduled(cron = "0 0 1 * * ?")
    fun generateUpcomingInstances() {
        val now = LocalDate.now()
        val generateUntil = now.plusDays(10)

        // 1. 查询所有在 generateUntil 前开始的活跃资源 id
        val activeRids: List<Long> = resourceService.findResourcesActiveBefore(generateUntil)

        activeRids.forEach {
            generateInstancesForResource(it, 10)
        }
    }

    /**
     * 为指定资源生成未来 N 天的实例（幂等）
     */
    @Transaction
    override fun generateInstancesForResource(rid: Long, days: Long) {
        val generateUntil = LocalDate.now().plusDays(days)
        val now = LocalDate.now()
//        val resource = resourceService.getById(rid)
        val resource = queryOne<Resource> { Resource::id eq rid }!!

        // 裁剪有效期， 不能早于今天 不能晚于资源结束日
        val windowStart = maxOf(resource.effectiveStart!!, now)
        val windowEnd = minOf(resource.effectiveEnd!!, generateUntil)
        if (windowStart > windowEnd) return

        // 获取该资源所有模板
        val templates = query<ResourceInstanceTemplate> {
            eq(ResourceInstanceTemplate::resourceId, rid)
        }

        templates.forEach { template ->
            // 生成候选实例
            val candidateInstances = generateCandidateInstances(resource, template, windowStart, windowEnd)
            if (candidateInstances.isEmpty()) return@forEach
            // 查询已存在的 startTime（仅当前 resource）
            val existingStartTimes = query<ResourceInstance> {
                select(ResourceInstance::startTime)
                eq(ResourceInstance::resourceId, rid)
                `in`(ResourceInstance::startTime, (candidateInstances.map { it.startTime }))
            }.map { it.startTime }.toSet()
            // 过滤已存在的实例
            val toInsert = candidateInstances.filterNot { existingStartTimes.contains(it.startTime) }
            if (toInsert.isNotEmpty()) {
//                saveBatch(toInsert)
                ResourceInstance::class.baseMapper.insertBatch(toInsert)
            }
        }
    }

    private fun generateCandidateInstances(
        resource: Resource,
        template: ResourceInstanceTemplate,
        windowStart: LocalDate,
        windowEnd: LocalDate
    ): List<ResourceInstance> {
        // 生成候选日期
        val dates = when (resource.repeatType) {
            "DAILY" -> DateUtils.eachDays(windowStart, windowEnd)
            "WEEKLY" -> {
                val allowedDays = template.daysOfWeek?.split(",")?.map { DayOfWeek.valueOf(it) }?.toSet()
                    ?: emptySet()
                DateUtils.eachDays(windowStart, windowEnd).filter { it.dayOfWeek in allowedDays }
            }

            "ONCE", "MONTHLY" -> {
                val allowedDays = template.daysOfMonth?.split(",")?.map { it.toInt() }?.toSet()
                    ?: emptySet()
                DateUtils.eachDays(windowStart, windowEnd).filter { it.dayOfMonth in allowedDays }
            }

            else -> emptyList()
        }
        // 生成候选实例
        return dates.map { date ->
            val bookableStartTimeAnchor = if (resource.bookingOpenMode == "FIXED_TIME") {
                date.atTime(0, 0)
            } else {
                date.atTime(template.startTime)
            }
            val bookingOpenBeforeSeconds = resource.bookingOpenBeforeSeconds?.toLong()
                ?: throw NullPointerException("resource.bookingOpenBeforeSeconds | id=${resource.id}")
            val startBookableTime = bookableStartTimeAnchor.minusSeconds(bookingOpenBeforeSeconds)
            ResourceInstance().apply {
                this.resourceId = template.resourceId
                this.startTime = date.atTime(template.startTime)
                this.endTime = date.atTime(template.endTime)
                this.startBookableTime = startBookableTime
                this.endBookableTime = if (resource.allowBookingOnUsage == 1) endTime else startTime
                this.capacityUnits = template.capacityUnits
                this.remainingUnits = template.capacityUnits
                this.status = 1
            }
        }
    }

    @Transaction(readOnly = true)
    override fun getResourceInstances(rid: Long): List<ResourceInstance> {
        return query<ResourceInstance> {
            eq(ResourceInstance::resourceId, rid)
        }
    }

    @Transaction(readOnly = true)
    override fun getResourceId(resourceInstanceId: Long): Long {
        return queryOne<ResourceInstance> {
            select(ResourceInstance::resourceId)
            eq(ResourceInstance::id, resourceInstanceId)
        }?.resourceId ?: throw NullPointerException("resourceInstance.resourceId | id=$resourceInstanceId")
    }

    @Transaction(readOnly = true)
    override fun getById(id: Long): ResourceInstance? {
        return queryOne<ResourceInstance> {
            ResourceInstance::id eq id
        }
    }
}
